﻿using System;
using System.Text;
using System.Windows;
using System.Windows.Browser;
using System.Windows.Controls;
using System.Windows.Markup;
using System.Windows.Shapes;
using System.Collections.Generic;
using System.Windows.Media;
using System.Windows.Resources;
using Stream = System.IO.Stream;

namespace Sonce.Editor
{
	public partial class Helper
	{
		#region Assembly
		public static string GetAssemblyName()
		{
#warning Assembly name is constant. Get it dyamically.
			//string assemblyName = this.GetType().Assembly.GetName().Name;
			//return typeof(Helper).Assembly.FullName;
			return "Sonce.Editor";
		}

		public static Stream GetResourceStream(string path)
		{
			string assemblyName = Helper.GetAssemblyName();
			string resourcePath = string.Format("/{0};component/", assemblyName) + path;
			StreamResourceInfo info = Application.GetResourceStream(new Uri(resourcePath, UriKind.Relative));
			return info.Stream;
		}
		#endregion

		#region General
		/// <summary>
		/// Create a Path shape.
		/// </summary>
		/// <param name="data">Path xaml data.</param>
		/// <returns>Path</returns>
		public static Path CreatePath(string data)
		{
			string template = @"<Path xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation"" Data=""{0}""/>";
			string xaml = string.Format(template, data);
			Path path = XamlReader.Load(xaml) as Path;
			return path;
		}

		/// <summary>
		/// Create a ControlTemplate element.
		/// </summary>
		/// <param name="data">Path xaml data.</param>
		/// <returns>Path</returns>
		public static ControlTemplate CreateControlTemplate(string xaml)
		{
			string controlTemplateXaml = xaml;
			int pos1 = controlTemplateXaml.IndexOf("<?");
			int pos2 = controlTemplateXaml.IndexOf("?>");
			if (pos1 >= 0 && pos2 > 0 && pos1 < pos2)
			{
				//Remove xml declaration
				controlTemplateXaml = controlTemplateXaml.Substring(pos2 + 2);
			}
			if (controlTemplateXaml.Contains("xmlns="))
			{
				//Remove xmlns to something other
				controlTemplateXaml = controlTemplateXaml.Replace("xmlns=", "xmlns:ignore=");
			}
			if (controlTemplateXaml.Contains("xmlns:x="))
			{
				//Remove xmlns:x to something other
				controlTemplateXaml = controlTemplateXaml.Replace("xmlns:x=", "xmlns:ignore_x=");
			}

			StringBuilder builder = new StringBuilder();
			builder.Append(@"<ControlTemplate xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation"" xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml"">");
			builder.Append(controlTemplateXaml);
			builder.Append("</ControlTemplate>");

			object obj = XamlReader.Load(builder.ToString());
			ControlTemplate template = obj as ControlTemplate;

			return template;
		}

		/// <summary>
		/// Create an object from xaml string.
		/// </summary>
		/// <param name="xaml">Xaml content as string.</param>
		/// <returns>FrameworkElement object.</returns>
		public static FrameworkElement LoadXaml(string xaml)
		{
			FrameworkElement result = null;
			if (!string.IsNullOrEmpty(xaml))
			{
				try
				{
#if SILVERLIGHT
					result = (FrameworkElement)XamlReader.Load(xaml);
#else
                    result = (FrameworkElement)XamlReader.Parse(xaml);
#endif
				}
				catch
				{
				}
			}
			return result;
		}

		/// <summary>
		/// Create an object from xaml string.
		/// </summary>
		/// <param name="xaml">Xaml content as string.</param>
		/// <returns>FrameworkElement object.</returns>
		public static FrameworkElement LoadXaml(Stream stream)
		{
			FrameworkElement result = null;
			if (stream != null && stream.CanRead)
			{
				string xaml = Helper.ReadStream(stream);
				result = Helper.LoadXaml(xaml);
			}
			return result;
		}

		public static Brush GetBrush(string hex)
		{
			return new SolidColorBrush(GetColor(hex));
		}

		public static Color GetColor(string hex)
		{
			Color color = Colors.Transparent;

			if (!string.IsNullOrEmpty(hex))
			{
				if (hex.StartsWith("#"))
				{
					hex = hex.Substring(1);
				}

				if (hex.Length == 8)
				{
					color = Color.FromArgb(
							Convert.ToByte(hex.Substring(0, 2), 16),
							Convert.ToByte(hex.Substring(2, 2), 16),
							Convert.ToByte(hex.Substring(4, 2), 16),
							Convert.ToByte(hex.Substring(6, 2), 16));
				}
				else if (hex.Length == 6)
				{
					color = Color.FromArgb(
							0xFF,
							Convert.ToByte(hex.Substring(0, 2), 16),
							Convert.ToByte(hex.Substring(2, 2), 16),
							Convert.ToByte(hex.Substring(4, 2), 16));
				}
			}

			return color;
		}

		public static string ReadStream(Stream stream)
		{
			int read = 0;
			const int size = 4 * 1024;
			byte[] buffer = new byte[size];
			StringBuilder builder = new StringBuilder();

			//Read whole stream
			while ((read = stream.Read(buffer, 0, size)) > 0)
			{
				builder.Append(Encoding.UTF8.GetString(buffer, 0, read));
			}

			return builder.ToString();
		}
		#endregion

		#region Math and geometry
		/// <summary>
		/// Get a rectangular region around the specified point. Size defines width and height or region, point defines offset.
		/// </summary>
		/// <param name="point">Offset as point.</param>
		/// <param name="size">Width and height as double,</param>
		/// <returns>Returns rectangular region.</returns>
		public static Rect GetRegion(Point point, double size)
		{
			return new Rect(point.X - size / 2, point.Y - size / 2, size, size);
		}

		/// <summary>
		/// Get the linear coefficient (y = k * x + n).
		/// </summary>
		/// <param name="point1">First coordinate.</param>
		/// <param name="point2">Second coordinate.</param>
		/// <returns>Returns linear coefficient or NaN when delta X equals 0.</returns>
		public static double GetLinearK(Point point1, Point point2)
		{
			if (point2.X == point1.X)
			{
				return double.NaN;
			}
			else
			{
				return (point2.Y - point1.Y) / (point2.X - point1.X);
			}
		}

		/// <summary>
		/// Get the length in pixels between the two points.
		/// </summary>
		/// <param name="point1">First coordinate.</param>
		/// <param name="point2">Second coordinate.</param>
		/// <returns>Returns distance between two coordinates.</returns>
		public static double GetDistance(Point point1, Point point2)
		{
			double dx = point2.X - point1.X;
			double dy = point2.Y - point1.Y;
			return Math.Sqrt(dx * dx + dy * dy);
		}

		public static Point[] GetOrtoLine(Point point1, Point point2, OrtoLines ortoLine)
		{
			Point[] result = new Point[2];
			switch (ortoLine)
			{
				default:
				case OrtoLines.None:
					result = new Point[2];
					result[0] = point1;
					result[1] = point2;
					break;

				case OrtoLines.Angle90:
					result = new Point[2];
					if (Math.Abs(point1.X - point2.X) > Math.Abs(point1.Y - point1.X))
					{
						result[0] = point1;
						result[1] = new Point(point2.X, point1.Y);
					}
					else
					{
						result[0] = point1;
						result[1] = new Point(point1.X, point2.Y);
					}
					break;

				case OrtoLines.Angle45:
					//TODO: Write algorithm to draw 45 degree lines

					//result = new Point[2];
					//result[0] = point1;

					//double angle = 0.0;
					//double k = (point2.Y - point1.Y) / (point2.X - point1.X);
					//double alpha = Math.Atan(k);
					//if (alpha < 22.5) angle = 
					//{
					//    result[1] = new Point(point2.X, point1.Y);
					//}

					//if (Math.Abs(point1.X - point2.X) > Math.Abs())
					//{
					//}
					//else
					//{
					//    result[0] = point1;
					//    result[1] = new Point(point1.X, point2.Y);
					//}
					break;

				case OrtoLines.Combo90:
					result = new Point[3];
					if (Math.Abs(point1.X - point2.X) > Math.Abs(point1.Y - point1.X))
					{
						result[0] = point1;
						result[1] = new Point(point2.X, point1.Y);
						result[2] = point2;
					}
					else
					{
						result[0] = point1;
						result[1] = new Point(point1.X, point2.Y);
						result[2] = point2;
					}
					break;

				case OrtoLines.Combo45:
					break;
			}
			return result;

			//string data = null;
			//switch (ortoLine)
			//{
			//    default:
			//    case OrtoLines.None:
			//        data = string.Format("M{0},{1} L{2},{3}", point1.X, point1.Y, point2.X, point2.Y);
			//        break;

			//    case OrtoLines.Angle90:
			//        if (Math.Abs(point1.X - point2.X) > Math.Abs(point1.Y - point1.X))
			//        {
			//            data = string.Format("M{0},{1} L{2},{3}", point1.X, point1.Y, point2.X, point1.Y);
			//        }
			//        else
			//        {
			//            data = string.Format("M{0},{1} L{2},{3}", point1.X, point1.Y, point1.X, point2.Y);
			//        }
			//        break;

			//    case OrtoLines.Angle45:
			//        break;

			//    case OrtoLines.Combo90:
			//        if (Math.Abs(point1.X - point2.X) > Math.Abs(point1.Y - point1.X))
			//        {
			//            data = string.Format("M{0},{1} L{2},{3} L{4},{5}", point1.X, point1.Y, point2.X, point1.Y, point2.X, point2.Y);
			//        }
			//        else
			//        {
			//            data = string.Format("M{0},{1} L{2},{3} L{4},{5}", point1.X, point1.Y, point1.X, point2.Y, point2.X, point2.Y);
			//        }
			//        break;

			//    case OrtoLines.Combo45:
			//        break;
			//}

			//Path path = Helper.CreatePath(data);
		}

		public static bool IsPointOnLine(Point[] polyline, Point point, double tolerance)
		{
			//Intersection region around point
			Rect region = new Rect(point.X - tolerance, point.Y - tolerance, 2 * tolerance, 2 * tolerance);
			bool result = Helper.Intersect(polyline, region);
			return result;
		}

		public static List<Point> GetPathPoints(FrameworkElement element)
		{
			List<Point> result = new List<Point>();

			//Get points from path
			if (element is Path)
			{
				#region Polyline
				Path polyline = element as Path;
				if (polyline != null && polyline.Data is PathGeometry)
				{
					PathGeometry geometry = polyline.Data as PathGeometry;
					foreach (PathFigure figure in geometry.Figures)
					{
						result.Add(figure.StartPoint);
						foreach (PathSegment segment in figure.Segments)
						{
							if (segment is LineSegment)
							{
								LineSegment line = segment as LineSegment;
								result.Add(line.Point);
							}
						}
					}
				}
				#endregion
			}
			else if (element is Rectangle)
			{
				//TODO: Get points
			}

			return result;
		}
		#endregion

		#region Insersection
		/// <summary>
		/// Get the intersection between polygon lines and rectangle edges.
		/// </summary>
		/// <param name="polygonPoints">Array of polygon points.</param>
		/// <param name="rectangle">Region for checking intersection with edges.</param>
		/// <returns>Returns true if polygon and rectangle intersect at least on one edge.</returns>
		public static bool Intersect(Point[] polygonPoints, Rect rectangle)
		{
			//TODO: Optimize rectangle and line intersection algorithm

			if (polygonPoints != null && polygonPoints.Length > 1)
			{
				Point lineStartPoint;
				Point lineEndPoint;
				Point intersection = new Point();

				//Top edge
				lineStartPoint = new Point(rectangle.Left, rectangle.Top);
				lineEndPoint = new Point(rectangle.Right, rectangle.Top);
				for (int i = 1; i < polygonPoints.Length; i++)
				{
					if (Intersect(lineStartPoint, lineEndPoint, polygonPoints[i - 1], polygonPoints[i], ref intersection))
					{
						return true;
					}
				}

				//Bottom edge
				lineStartPoint = new Point(rectangle.Left, rectangle.Bottom);
				lineEndPoint = new Point(rectangle.Right, rectangle.Bottom);
				for (int i = 1; i < polygonPoints.Length; i++)
				{
					if (Intersect(lineStartPoint, lineEndPoint, polygonPoints[i - 1], polygonPoints[i], ref intersection))
					{
						return true;
					}
				}

				//Left edge
				lineStartPoint = new Point(rectangle.Left, rectangle.Top);
				lineEndPoint = new Point(rectangle.Left, rectangle.Bottom);
				for (int i = 1; i < polygonPoints.Length; i++)
				{
					if (Intersect(lineStartPoint, lineEndPoint, polygonPoints[i - 1], polygonPoints[i], ref intersection))
					{
						return true;
					}
				}

				//Right edge
				lineStartPoint = new Point(rectangle.Right, rectangle.Top);
				lineEndPoint = new Point(rectangle.Right, rectangle.Bottom);
				for (int i = 1; i < polygonPoints.Length; i++)
				{
					if (Intersect(lineStartPoint, lineEndPoint, polygonPoints[i - 1], polygonPoints[i], ref intersection))
					{
						return true;
					}
				}
			}

			return false;


			//Intersection.Intersection result;
			//Point lineStartPoint;
			//Point lineEndPoint;

			////Top edge
			//lineStartPoint = new Point(rectangle.Left, rectangle.Top);
			//lineEndPoint = new Point(rectangle.Right, rectangle.Top);
			//result = Sonce.Editor.Intersection.Geometry.IntersectionOf(
			//    new Sonce.Editor.Intersection.Line(lineStartPoint, lineEndPoint),
			//    new Sonce.Editor.Intersection.Polygon(polygonPoints));
			//if (result == Sonce.Editor.Intersection.Intersection.Intersection)
			//{
			//    return true;
			//}

			////Bottom edge
			//lineStartPoint = new Point(rectangle.Left, rectangle.Bottom);
			//lineEndPoint = new Point(rectangle.Right, rectangle.Bottom);
			//result = Sonce.Editor.Intersection.Geometry.IntersectionOf(
			//    new Sonce.Editor.Intersection.Line(lineStartPoint, lineEndPoint),
			//    new Sonce.Editor.Intersection.Polygon(polygonPoints));
			//if (result == Sonce.Editor.Intersection.Intersection.Intersection)
			//{
			//    return true;
			//}





			//return false;

			//bool intersect = false;

			////Compare top edge
			//// y = kx + n
			//// k = dy/dx, n = y0
			//double kSelection = 0;
			//double kLine = endPoint.Y - startPoint.Y / endPoint.X - endPoint.Y;
			//double nSelection = selection.Top;
			//double nLine = startPoint.Y;
			//if (kLine != kSelection) //If not parallel
			//{
			//    double x = nSelection - nLine / kLine - kSelection;
			//    double y = kSelection * x + nSelection;

			//    intersect = true;
			//    intersect &= Math.Abs(startPoint.X - endPoint.X) < Math.Abs(x);
			//    intersect &= Math.Abs(startPoint.Y - endPoint.Y) < Math.Abs(y);
			//    intersect &= Math.Abs(selection.Left - selection.Right) < Math.Abs(x);
			//    intersect &= Math.Abs(selection.Top - selection.Bottom) < Math.Abs(y);
			//    if (intersect)
			//    {
			//        return true;
			//    }
			//}

			////Compare bottom edge
			//nSelection = selection.Bottom;

			//return intersect;
		}

		/// <summary>
		/// Get the intersection between two lines.
		/// </summary>
		/// <param name="line1Point1"></param>
		/// <param name="line1Point2"></param>
		/// <param name="line2Point1"></param>
		/// <param name="line2Point2"></param>
		/// <param name="intersection"></param>
		/// <returns></returns>
		public static bool Intersect(Point line1Point1, Point line1Point2, Point line2Point1, Point line2Point2, ref Point intersection)
		{ // Based on the 2d line intersection method from "comp.graphics.algorithms
			//Frequently Asked Questions"

			/*
						 (Ay-Cy)(Dx-Cx)-(Ax-Cx)(Dy-Cy)
				 r = -----------------------------  (eqn 1)
						 (Bx-Ax)(Dy-Cy)-(By-Ay)(Dx-Cx)
			*/

			double q = (line1Point1.Y - line2Point1.Y) * (line2Point2.X - line2Point1.X) - (line1Point1.X - line2Point1.X) * (line2Point2.Y - line2Point1.Y);
			double d = (line1Point2.X - line1Point1.X) * (line2Point2.Y - line2Point1.Y) - (line1Point2.Y - line1Point1.Y) * (line2Point2.X - line2Point1.X);

			if (d == 0) // parallel lines so no intersection anywhere in space (in
			//curved space, maybe, but not here in Euclidian space.)
			{
				return false;
			}

			double r = q / d;

			/*
						 (Ay-Cy)(Bx-Ax)-(Ax-Cx)(By-Ay)
				 s = -----------------------------  (eqn 2)
						 (Bx-Ax)(Dy-Cy)-(By-Ay)(Dx-Cx)
			*/

			q = (line1Point1.Y - line2Point1.Y) * (line1Point2.X - line1Point1.X) -
		(line1Point1.X - line2Point1.X) * (line1Point2.Y - line1Point1.Y);
			double s = q / d;

			/*
						 If r>1, P is located on extension of AB
						 If r<0, P is located on extension of BA
						 If s>1, P is located on extension of CD
						 If s<0, P is located on extension of DC

						 The above basically checks if the intersection is located at an
			extrapolated
						 point outside of the line segments. To ensure the intersection is
			only within
						 the line segments then the above must all be false, ie r between 0
			and 1
						 and s between 0 and 1.
			*/

			if (r < 0 || r > 1 || s < 0 || s > 1)
			{
				return false;
			}

			/*
						 Px=Ax+r(Bx-Ax)
						 Py=Ay+r(By-Ay)
			*/

			intersection.X = line1Point1.X + (int)(0.5f + r * (line1Point2.X -
		line1Point1.X));
			intersection.Y = line1Point1.Y + (int)(0.5f + r * (line1Point2.Y -
		line1Point1.Y));
			return true;
		}

		/// <summary>
		/// Get the intersection between two rectangles.
		/// </summary>
		/// <param name="rect1"></param>
		/// <param name="rect2"></param>
		/// <returns></returns>
		public static bool Intersect(Rect rect1, Rect rect2)
		{
			rect1.Intersect(rect2);
			return !rect1.IsEmpty;
		}
		#endregion

		#region JavaScript
		public static void RegisterMouseWheel()
		{
			//Work-around to use mouse wheel in Silverlight 2.0
			if (HtmlPage.IsEnabled)
			{
				HtmlPage.Window.AttachEvent("DOMMouseScroll", OnMouseWheel);
				HtmlPage.Window.AttachEvent("onmousewheel", OnMouseWheel);
				HtmlPage.Document.AttachEvent("onmousewheel", OnMouseWheel);
			}
		}

		public static event EventHandler<EventArgs<int>> MouseWheelChanged;

		protected static void OnMouseWheel(object sender, HtmlEventArgs args)
		{
			if (MouseWheelChanged != null)
			{
				//Reference: http://silverlight.net/blogs/msnow/archive/2008/07/29/tip-of-the-day-23-how-to-capture-the-mouse-wheel-event.aspx

				double mouseDelta = 0;
				ScriptObject e = args.EventObject;

				//Mozilla and Safari
				if (e.GetProperty("detail") != null)
				{
					mouseDelta = ((double)e.GetProperty("detail"));
				}

				//IE and Opera
				else if (e.GetProperty("wheelDelta") != null)
				{
					mouseDelta = ((double)e.GetProperty("wheelDelta"));
				}

				int delta = Math.Sign(mouseDelta);

				//if (mouseDelta > 0)
				//{
				//    runCommand(Commands.ZoomIn);
				//}
				//else
				//{
				//    runCommand(Commands.ZoomOut);
				//}

				MouseWheelChanged(sender, new EventArgs<int>(delta));
			}
		}
		#endregion
	}
}
